package core;


import com.distribute.utils.JsonUtils;
import config.DSConfig;
import constant.DSConstants;
import core.api.ApiClient;
import core.api.AuthManager;
import core.api.RetryPolicy;
import core.component.DefaultRetryPolicy;
import core.component.auth.SessionAuthenticator;
import core.component.interceptor.SessionInterceptor;
import exception.ApiException;
import exception.RetryException;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;

import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static constant.MediaTypeConstants.MEDIA_TYPE_FORM;
import static constant.MediaTypeConstants.MEDIA_TYPE_JSON;

public class DSApiClient implements ApiClient {

    private final DSConfig dsConfig;

    private final String baseUrl;

    private RetryPolicy retryPolicy = new DefaultRetryPolicy();

    private final OkHttpClient okHttpClient;

    private Executor asyncExecutor;

    public DSApiClient(DSConfig dsConfig) {
        this.dsConfig = dsConfig;
        this.baseUrl = StringUtils.removeEnd(dsConfig.getUrl(), "/");
        okHttpClient = new OkHttpClient.Builder().authenticator(new SessionAuthenticator(dsConfig)).addInterceptor(new SessionInterceptor(dsConfig)).connectTimeout(dsConfig.getTimeout(), TimeUnit.SECONDS).callTimeout(dsConfig.getTimeout(), TimeUnit.SECONDS).build();
    }


    @Override
    public <T> T get(String path, Class<T> responseType, Object... uriVariables) {
        return executeWithRetry(() -> {
            String url = buildUrl(path, uriVariables);
            Request request = new Request.Builder().url(url).get().build();
            return executeRequest(request, responseType);
        });
    }

    @Override
    public <T> T post(String path, Object body, MediaType mediaType, Class<T> responseType, Object... uriVariables) {
        return executeWithRetry(() -> {
            String url = buildUrl(path, uriVariables);
            RequestBody requestBody = prepareRequestBody(body, mediaType);
            Request request = new Request.Builder().url(url).post(requestBody).build();
            return executeRequest(request, responseType);
        });
    }

    @Override
    public <T> T put(String path, Object body, MediaType mediaType, Class<T> responseType, Object... uriVariables) {
        return executeWithRetry(() -> {
            String url = buildUrl(path, uriVariables);
            RequestBody requestBody = prepareRequestBody(body, mediaType);
            Request request = new Request.Builder().url(url).put(requestBody).build();
            return executeRequest(request, responseType);
        });
    }

    @Override
    public <T> T delete(String path, Object body, MediaType mediaType, Class<T> responseType, Object... uriVariables) {
        return executeWithRetry(() -> {
            String url = buildUrl(path, uriVariables);
            RequestBody requestBody = prepareRequestBody(body, mediaType);
            Request request = new Request.Builder().url(url).delete(requestBody).build();
            return executeRequest(request, responseType);
        });
    }

    @Override
    public <T> CompletableFuture<T> getAsync(String path, Class<T> responseType, Object... uriVariables) {
        return CompletableFuture.supplyAsync(() -> get(path, responseType, uriVariables), asyncExecutor);
    }

    @Override
    public <T> CompletableFuture<T> postAsync(String path, Object requestBody, MediaType mediaType, Class<T> responseType, Object... uriVariables) {
        return CompletableFuture.supplyAsync(() -> post(path, requestBody, mediaType, responseType, uriVariables), asyncExecutor);
    }

    @Override
    public DSConfig getConfig() {
        return dsConfig;
    }


    /**
     * 构建路径
     *
     * @param path
     * @param uriVariables
     * @return
     */
    private String buildUrl(String path, Object... uriVariables) {
        path = StringUtils.removeStart(path, "/");
        return "http://" + baseUrl + "/" + DSConstants.DS + "/" + String.format(path, uriVariables);
    }

    private <T> T executeRequest(Request request, Class<T> responseType) {
        try {
            Response response = okHttpClient.newCall(request).execute();
            if (!response.isSuccessful()) {
                throw new ApiException("Request failed with status" + response.code());
            }

            if (Void.class.equals(responseType)) {
                return null;
            }

            assert response.body() != null;
            String body = response.body().string();
            if (String.class.equals(responseType)) {
                return (T) body;
            }

            return JsonUtils.toBean(body, responseType);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private RequestBody prepareRequestBody(Object body, MediaType mediaType) {
        if (body == null) {
            return null;
        }
        if (mediaType.equals(MEDIA_TYPE_JSON)) {
            return handleJsonBody(body);
        } else if (mediaType.equals(MEDIA_TYPE_FORM)) {
            return handleFormUrlEncodedBody(body);
//            case MEDIA_TYPE_MULTIPART:
//                handleMultipartBody(body);
        }
        throw new IllegalArgumentException("Unsupported content type:" + mediaType);
    }

    private RequestBody handleFormUrlEncodedBody(Object body) {
        if (body instanceof Map) {
            @SuppressWarnings("unchecked") Map<String, Object> formData = (Map<String, Object>) body;
            FormBody.Builder builder = new FormBody.Builder();
            formData.forEach((k, v) -> {
                builder.add(k, String.valueOf(v));
            });
            return builder.build();
        } else {
            throw new IllegalArgumentException("Form data must be a Map for application/x-www-form-urlencoded");
        }
    }

    private RequestBody handleJsonBody(Object body) {
        if (body instanceof String) {
            return RequestBody.create((String) body, MEDIA_TYPE_JSON);
        } else {
            String jsonString = JsonUtils.converString(body);
            return RequestBody.create(jsonString, MEDIA_TYPE_JSON);
        }
    }

    private <T> T executeWithRetry(Supplier<T> operation) {
        int retryCount = 0;
        Exception lastException = null;

        //
        while (retryCount <= retryPolicy.getMaxAttempts()) {
            try {
                return operation.get();
            } catch (Exception e) {
                lastException = e;

                // 重试策略 判断 是否需要进行重试
                if (!retryPolicy.shouldRetry(retryCount, lastException)) {
                    break;
                }

                retryCount++;
            }
        }

        throw new RetryException("Request failed after" + retryCount + "attempts", lastException);
    }

    public RetryPolicy getRetryPolicy() {
        return retryPolicy;
    }

    public void setRetryPolicy(RetryPolicy retryPolicy) {
        this.retryPolicy = retryPolicy;
    }


    public Executor getAsyncExecutor() {
        return asyncExecutor;
    }

    public void setAsyncExecutor(Executor asyncExecutor) {
        this.asyncExecutor = asyncExecutor;
    }

}
